package com.ruihu.admin.module.business.tasknode.manager;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.ruihu.admin.common.constant.GlobalConst;
import com.ruihu.admin.common.constant.JudgeEnum;
import com.ruihu.admin.common.constant.ResponseCodeConst;
import com.ruihu.admin.common.constant.SuccessEnum;
import com.ruihu.admin.common.domain.ResponseDTO;
import com.ruihu.admin.constant.CommonConst;
import com.ruihu.admin.module.business.log.aiimagelog.dao.AiImageLogDao;
import com.ruihu.admin.module.business.log.aiimagelog.domain.entity.AiImageLogEntity;
import com.ruihu.admin.module.business.log.interfacelog.domain.entity.InterfaceLogEntity;
import com.ruihu.admin.module.business.task.constant.TaskStatusEnum;
import com.ruihu.admin.module.business.task.dao.SubTaskDao;
import com.ruihu.admin.module.business.task.dao.SubtaskDefectDao;
import com.ruihu.admin.module.business.task.dao.TaskDao;
import com.ruihu.admin.module.business.task.domain.dto.SubTaskQueryDTO;
import com.ruihu.admin.module.business.task.domain.entity.SubTaskEntity;
import com.ruihu.admin.module.business.task.domain.entity.SubtaskDefectEntity;
import com.ruihu.admin.module.business.task.domain.entity.TaskEntity;
import com.ruihu.admin.module.business.tasknode.dao.*;
import com.ruihu.admin.module.business.tasknode.domain.dto.*;
import com.ruihu.admin.module.business.tasknode.domain.entity.*;
import com.ruihu.admin.module.support.file.constant.FileModuleTypeEnum;
import com.ruihu.admin.module.support.file.constant.FileSourceEnum;
import com.ruihu.admin.module.support.file.dao.FileDao;
import com.ruihu.admin.module.support.file.domain.dto.FileUpdateDTO;
import com.ruihu.admin.module.support.file.domain.entity.FileEntity;
import com.ruihu.admin.module.support.task.domain.AIErrorFile;
import com.ruihu.admin.util.RequestTokenUtil;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.jetbrains.annotations.NotNull;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class TaskNodeInstManager {

    @Autowired
    private SubTaskDao subTaskDao;

    @Autowired
    private SubTaskNodeInstDao subTaskNodeInstDao;

    @Autowired
    private TaskDao taskDao;

    @Autowired
    private DefectTypeDao defectTypeDao;

    @Autowired
    private TaskNodeDao taskNodeDao;

    @Autowired
    private AiImageLogDao aiImageLogDao;

    @Autowired
    private FileDao fileDao;

    @Autowired
    private TaskNodeFileDao taskNodeFileDao;

    @Autowired
    private NodeInstProcessRelationDao nodeInstProcessRelationDao;

    @Autowired
    private NodeInstProcessDefectDao nodeInstProcessDefectDao;

    @Autowired
    private NodeDefectRelationDao nodeDefectRelationDao;

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    @Autowired
    private SetOperations<String, Object> setOperations;

    @Value("${file-upload-service.path}")
    private String fileBasePath;

    /**
     * 新增任务节点实例数据
     * @param addDTO
     * @return 返回SubTaskNodeInstIdEntity, 同时会将动态表单元素ID赋值到新增对象中
     */
    @Transactional(rollbackFor = Exception.class)
    public ResponseDTO<SubTaskNodeInstEntity> addTaskNodeInst(SubTaskNodeInstAddDTO addDTO) {
        SubTaskNodeInstEntity subTaskNodeInstEntity = SubTaskNodeInstEntity.convertToSubTaskNodeInstEntity(addDTO);
        if (Objects.nonNull(RequestTokenUtil.getRequestUser()))
            subTaskNodeInstEntity.setCreateBy(RequestTokenUtil.getRequestUser().getRequestUserId());
        subTaskNodeInstEntity.setCreateTime(new Date());
        subTaskNodeInstEntity.setRevision(CommonConst.DEFAULT_OPTI_LOCK_REVISION);
        this.subTaskNodeInstDao.insert(subTaskNodeInstEntity);
        return ResponseDTO.succData(subTaskNodeInstEntity);
    }

    @NotNull
    private TaskNodeFileEntity getTaskNodeFileEntity(SubTaskNodeInstEntity subTaskNodeInstEntity, FileEntity fileEntity, Long processRelationId) {
        TaskNodeFileEntity taskNodeFileEntity = new TaskNodeFileEntity();
        taskNodeFileEntity.setSubTaskId(subTaskNodeInstEntity.getSubTaskId());
        taskNodeFileEntity.setTaskNodeInstId(subTaskNodeInstEntity.getTaskNodeInstId());
        taskNodeFileEntity.setSubTaskNodeInstId(subTaskNodeInstEntity.getId());
        taskNodeFileEntity.setProcessRelationId(processRelationId);
        taskNodeFileEntity.setFileId(fileEntity.getId());
        taskNodeFileEntity.setRevision(CommonConst.DEFAULT_OPTI_LOCK_REVISION);
        taskNodeFileEntity.setCreateBy(RequestTokenUtil.getRequestUser().getRequestUserId());
        taskNodeFileEntity.setCreateTime(new Date());
        return taskNodeFileEntity;
    }

    @Transactional(rollbackFor = Exception.class)
    public ResponseDTO<SubTaskNodeInstEntity> updateTaskNodeInst(SubTaskNodeInstEntity subTaskNodeInstEntity,SubTaskNodeInstUpdateDTO updateDTO) {
        subTaskNodeInstEntity = SubTaskNodeInstEntity.convertToSubTaskNodeInstEntity(subTaskNodeInstEntity, updateDTO);
        this.subTaskNodeInstDao.updateById(subTaskNodeInstEntity);
        return ResponseDTO.succData(subTaskNodeInstEntity);
    }

    @Transactional(rollbackFor = Exception.class)
    public ResponseDTO<Long> updateSubTaskNode(SubTaskEntity subTaskEntity,
                                               SubTaskNodeInstEntity subTaskNodeInstEntity,
                                               SubTaskNodeInstUpdateDTO updateDTO,
                                               Long nextTaskNodeInstId) {
        subTaskNodeInstEntity = SubTaskNodeInstEntity.convertToSubTaskNodeInstEntity(subTaskNodeInstEntity, updateDTO);
        int affectCount = this.subTaskNodeInstDao.updateById(subTaskNodeInstEntity);
        if (affectCount > 0) {
            // 删除的缺陷类型ID列表
            SqlSession sqlSession = this.sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
            // 保存动态配置表单元素
            if (CollectionUtil.isNotEmpty(updateDTO.getNodeInstConfigValueList())) {
                List<NodeInstProcessRelationUpdateDTO> nodeInstProcessRelationUpdateDTOList = updateDTO.getNodeInstConfigValueList();
                Long subTaskNodeInstId = subTaskNodeInstEntity.getId();
                Long currentTaskNodeInstId = subTaskNodeInstEntity.getTaskNodeInstId();
                Map<String, List<FileUpdateDTO>> fileUpdateDTOMap = new HashMap<>();
                // 新增元素
                List<NodeInstProcessRelationUpdateDTO> newNodeProcessRelationList = nodeInstProcessRelationUpdateDTOList.stream().filter(e -> Objects.isNull(e.getId())).collect(Collectors.toList());
                newNodeProcessRelationList.stream().forEach(e -> {
                    NodeInstProcessRelationEntity nodeInstProcessRelationEntity = NodeInstProcessRelationEntity.convertToNodeInstProcessRelationEntity(e);
                    nodeInstProcessRelationEntity.setTaskNodeInstId(currentTaskNodeInstId);
                    nodeInstProcessRelationEntity.setSubTaskNodeInstId(subTaskNodeInstId);
                    nodeInstProcessRelationEntity.setCreateTime(new Date());
                    this.nodeInstProcessRelationDao.insert(nodeInstProcessRelationEntity);
                    sqlSession.commit();
                    if (CollectionUtil.isNotEmpty(e.getFileList())) {
                        fileUpdateDTOMap.put(nodeInstProcessRelationEntity.getId().toString(), e.getFileList());
                    }
                });

                // 更新元素
                List<NodeInstProcessRelationUpdateDTO> updateNodeProcessRelationList = nodeInstProcessRelationUpdateDTOList.stream().filter(e -> Objects.nonNull(e.getId())).collect(Collectors.toList());
                List<Long> idList = updateNodeProcessRelationList.stream().map(NodeInstProcessRelationUpdateDTO::getId).collect(Collectors.toList());
                // 查出所有已存在的元素
                List<NodeInstProcessRelationEntity> existsNodeInstProcessRelationEntityList = this.nodeInstProcessRelationDao.selectBatchIds(idList);
                updateNodeProcessRelationList.stream().forEach(e -> {
                    // 查寻取消选择的缺陷类型
                    Optional<NodeInstProcessRelationEntity> optional = existsNodeInstProcessRelationEntityList.stream().filter(x -> x.getId().equals(e.getId())).findFirst();
                    if (optional.isPresent()) {
                        NodeInstProcessRelationEntity existsEntity = optional.get();
                        NodeInstProcessRelationEntity nodeInstProcessRelationEntity = NodeInstProcessRelationEntity.convertToNodeInstProcessRelationEntity(e);
                        NodeInstProcessRelationEntity.copyIgnoreNullAndEmpty(nodeInstProcessRelationEntity, existsEntity);
                        existsEntity.setUpdateTime(new Date());
                        existsEntity.setSubTaskNodeInstId(subTaskNodeInstId);

                        if (CollectionUtil.isNotEmpty(e.getFileList())) {
                            fileUpdateDTOMap.put(e.getId().toString(), e.getFileList());
                        }
                        this.nodeInstProcessRelationDao.updateByIdIgnoreEmptyValue(existsEntity);
                    }
                });
                sqlSession.commit();

                // 保存附件关联
                if (CollectionUtil.isNotEmpty(fileUpdateDTOMap)) {
                    // 保存文件到附件表
                    String insertFileSqlId = FileDao.class.getName() + ".insert";
                    Map<String, List<FileEntity>> fileEntityMap = new HashMap<>(fileUpdateDTOMap.size());
                    fileUpdateDTOMap.forEach((key, fileUpdateDTOList) -> {
                        fileEntityMap.put(key, new ArrayList<>());

                        fileUpdateDTOList.forEach(fileUpdateDTO -> {
                            if (Objects.isNull(fileUpdateDTO.getId())) {
                                FileEntity fileEntity = FileEntity.convertToFileEntity(fileUpdateDTO);
                                fileEntity.setSource(FileSourceEnum.LOCAL.getValue());
                                fileEntity.setCreaterUser(RequestTokenUtil.getRequestUser().getRequestUserId());
                                fileEntity.setCreateTime(new Date());
                                sqlSession.insert(insertFileSqlId, fileEntity);
                                fileEntityMap.get(key).add(fileEntity);
                            }
                        });
                    });
                    sqlSession.commit();

                    // 保存新的文件关联到任务节点实例表
                    String sqlId = TaskNodeFileDao.class.getName() + ".insert";
                    String finalSqlId = sqlId;
                    SubTaskNodeInstEntity subTaskNodeInstEntity1 = subTaskNodeInstEntity;
                    fileEntityMap.forEach((processRelationId, fileEntityList) -> {
                        fileEntityList.forEach(fileEntity -> {
                            TaskNodeFileEntity taskNodeFileEntity = getTaskNodeFileEntity(subTaskNodeInstEntity1, fileEntity, Long.valueOf(processRelationId));
                            this.taskNodeFileDao.insert(taskNodeFileEntity);
                            sqlSession.insert(finalSqlId, taskNodeFileEntity);
                        });
                    });
                    sqlSession.commit();
                }
            }

            // 处理缺陷类型
            Long subTaskId = subTaskNodeInstEntity.getSubTaskId();
            Long taskNodeInstId = subTaskNodeInstEntity.getTaskNodeInstId();
            NodeInstProcessDefectQueryDTO queryDTO = new NodeInstProcessDefectQueryDTO();
            queryDTO.setSubTaskId(subTaskNodeInstEntity.getSubTaskId());
            queryDTO.setTaskNodeInstId(subTaskNodeInstEntity.getTaskNodeInstId());
            queryDTO.setProcessRelationIdIsNull(JudgeEnum.YES.getValue());
            List<NodeInstProcessDefectDTO> nodeInstProcessDefectDTOList = this.nodeInstProcessDefectDao.queryByList(queryDTO);
            Map<Long, NodeInstProcessDefectDTO> existsNodeInstProcessDefectMap = nodeInstProcessDefectDTOList.stream()
                    .collect(Collectors.toMap(NodeInstProcessDefectDTO::getDefectTypeId, e -> e));
            List<DefectTypeDTO> defectTypeDTOList = updateDTO.getDefectTypeList() == null ? new ArrayList<>() : updateDTO.getDefectTypeList();
            if (CollectionUtil.isNotEmpty(defectTypeDTOList)) {
                // 保存新的缺陷类型
                updateDTO.getDefectTypeList().forEach(o -> {
                    if (!existsNodeInstProcessDefectMap.containsKey(o.getDefectTypeId())) {
                        NodeInstProcessDefectEntity nodeInstProcessDefectEntity = new NodeInstProcessDefectEntity();
                        nodeInstProcessDefectEntity.setSubTaskId(subTaskId);
                        nodeInstProcessDefectEntity.setTaskNodeInstId(taskNodeInstId);
                        nodeInstProcessDefectEntity.setDefectTypeId(o.getDefectTypeId());
                        nodeInstProcessDefectEntity.setDefectName(o.getDefectName());
                        nodeInstProcessDefectEntity.setCreateBy(RequestTokenUtil.getThreadLocalUser().getRequestUserId());
                        nodeInstProcessDefectEntity.setCreateTime(new Date());
                        this.nodeInstProcessDefectDao.insert(nodeInstProcessDefectEntity);
                    }
                });
            }

            // 删除没有选中的缺陷类型
            if (defectTypeDTOList.size() > 0) {
                Set<Long> defectTypeIdSet = defectTypeDTOList.stream().collect(Collectors.mapping(DefectTypeDTO::getDefectTypeId, Collectors.toSet()));
                List<Long> delIdList = new ArrayList<>();
                nodeInstProcessDefectDTOList.forEach(e -> {
                    if (!defectTypeIdSet.contains(e.getDefectTypeId()))
                        delIdList.add(e.getId());
                });
                this.nodeInstProcessDefectDao.deleteBatchIds(delIdList);
                sqlSession.commit();
            }
        }

        if (Objects.nonNull(nextTaskNodeInstId) && nextTaskNodeInstId != subTaskEntity.getCurrentTaskNodeInstId()) {
            subTaskEntity.setElectricCoreCode(updateDTO.getElectricCoreCode());
            subTaskEntity.setElectricCoreType(updateDTO.getElectricCoreType());
            subTaskEntity.setCurrentTaskNodeInstId(nextTaskNodeInstId);
            subTaskEntity.setUpdateTime(new Date());
            subTaskEntity.setUpdateBy(RequestTokenUtil.getRequestUser().getRequestUserId());
            this.subTaskDao.updateByIdIgnoreEmptyValue(subTaskEntity);
            if (!this.setOperations.isMember(CommonConst.REDIS_ELECTRIC_CORE_CODE_SET_NAME, subTaskEntity.getElectricCoreCode()))
                this.setOperations.add(CommonConst.REDIS_ELECTRIC_CORE_CODE_SET_NAME, subTaskEntity.getElectricCoreCode());
        }

        return ResponseDTO.succData(subTaskNodeInstEntity.getId());
    }

    @NotNull
    private SubtaskDefectEntity getSubtaskDefectEntity(SubTaskNodeInstAddDTO addDTO, Long defectTypeId) {
        SubtaskDefectEntity subtaskDefectEntity = new SubtaskDefectEntity();
        subtaskDefectEntity.setSubTaskId(addDTO.getSubTaskId());
        subtaskDefectEntity.setDefectTypeId(defectTypeId);
        subtaskDefectEntity.setRevision(CommonConst.DEFAULT_OPTI_LOCK_REVISION);
        subtaskDefectEntity.setCreateBy(RequestTokenUtil.getRequestUser().getRequestUserId());
        subtaskDefectEntity.setCreateTime(new Date());
        return subtaskDefectEntity;
    }

    @Transactional(rollbackFor = Exception.class)
    public ResponseDTO<List<AIErrorFile>> updateNode11(List<AIErrorFile> aiErrorFileList, Long subTaskId, Long taskNodeInstId) {
        List<AIErrorFile> successFileList = new ArrayList<>();
        SubTaskEntity subTaskEntity = this.subTaskDao.selectById(subTaskId);

        SqlSession sqlSession = this.sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
        List<FileEntity> fileEntityList = new ArrayList<>(aiErrorFileList.size());
        for (int i = 0; i < aiErrorFileList.size(); i++) {
            File file = aiErrorFileList.get(i).getFile();
            FileEntity fileEntity = new FileEntity();
            fileEntity.setModuleId("1");
            fileEntity.setModuleType("1");
            fileEntity.setFileOrigName(file.getName());
            fileEntity.setFileName(file.getName());
            fileEntity.setFileSize(String.valueOf(file.length()));
            String subPath = FileModuleTypeEnum.TASK_NODE.getPath() + File.separator + subTaskEntity.getTaskCode();
            fileEntity.setFilePath(subPath + File.separator + file.getName());
            fileEntity.setFileType(FileUtil.extName(file));
            fileEntity.setFileLocationType(1);
            fileEntity.setSource(FileSourceEnum.AI.getValue());
            fileEntity.setCreateTime(new Date());
            fileEntity.setRemark(aiErrorFileList.get(i).getDesc());
            this.fileDao.insert(fileEntity);

            File destFile = new File(this.fileBasePath + File.separator + subPath + File.separator + file.getName());
            FileUtil.copy(file, destFile, true);
            log.info("Copy the file for the electric core {} success >>> {}", aiErrorFileList.get(i).getElectricCoreCode(), destFile.getAbsolutePath());

            fileEntityList.add(fileEntity);
        }
        sqlSession.commit();

        for (int i = 0; i < fileEntityList.size(); i++) {
            TaskNodeFileEntity taskNodeFileEntity = new TaskNodeFileEntity();
            taskNodeFileEntity.setSubTaskId(subTaskId);
            taskNodeFileEntity.setTaskNodeInstId(taskNodeInstId);
            taskNodeFileEntity.setFileId(fileEntityList.get(i).getId());
            taskNodeFileEntity.setRemark(fileEntityList.get(i).getRemark());
            taskNodeFileEntity.setRevision(CommonConst.DEFAULT_OPTI_LOCK_REVISION);
            taskNodeFileEntity.setCreateTime(new Date());
            this.taskNodeFileDao.insert(taskNodeFileEntity);
            successFileList.add(aiErrorFileList.get(i));
        }
        sqlSession.commit();

        return ResponseDTO.succData(successFileList);
    }

    /**
     * 移动AI图片
     * @param aiErrorFileList
     * @param interfaceLogEntity
     * @return
     */
    public ResponseDTO<String> copyAIImage(List<AIErrorFile> aiErrorFileList, InterfaceLogEntity interfaceLogEntity) {
        Assert.notNull(interfaceLogEntity);

        if (CollectionUtil.isEmpty(aiErrorFileList))
            return ResponseDTO.wrap(ResponseCodeConst.LIST_EMPTY);

        SubTaskEntity subTaskEntity = this.subTaskDao.selectById(interfaceLogEntity.getSubTaskId());
        if (Objects.isNull(subTaskEntity))
            return ResponseDTO.wrap(ResponseCodeConst.NOT_EXISTS);

        SqlSession sqlSession = this.sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
        aiErrorFileList.forEach(aiErrorFile -> {
            String subPath = this.fileBasePath + File.separator
                    + FileModuleTypeEnum.TASK_NODE.getPath()
                    + File.separator + subTaskEntity.getTaskCode()
                    + File.separator + aiErrorFile.getElectricCoreCode()
                    + File.separator + aiErrorFile.getPoleGroupNumber();
            File destFile = new File(subPath + File.separator + aiErrorFile.getFile().getName());
            FileUtil.copy(aiErrorFile.getFile(), destFile, true);
            log.info("Copy the image file success, electric info {},{}  >>> {}", aiErrorFile.getElectricCoreCode(), aiErrorFile.getPoleGroupNumber(), destFile.getAbsolutePath());
            // 压缩图片
            try {
                byte[] imageBytes = FileUtil.readBytes(destFile);
                File thumbnailFile = new File(subPath + File.separator + CommonConst.THUMBNAIL_PREFIX + aiErrorFile.getFile().getName());
                Thumbnails.of(new ByteArrayInputStream(imageBytes)).scale(0.4).toFile(thumbnailFile);
                log.info("Comparess image file success {}", destFile.getAbsolutePath());
            } catch (IOException e) {
                log.error("Compress image file error.", e);
            }

            AiImageLogEntity aiImageLogEntity = new AiImageLogEntity();
            aiImageLogEntity.setInterfaceLogId(interfaceLogEntity.getId());
            aiImageLogEntity.setFileName(aiErrorFile.getFile().getName());
            aiImageLogEntity.setResult(SuccessEnum.SUCCESS.getValue());
            aiImageLogEntity.setCreateTime(new Date());
            this.aiImageLogDao.insert(aiImageLogEntity);

            String defectType = aiErrorFile.getDefectType();
            if (StrUtil.isNotEmpty(defectType)) {
                String[] defectTypeArray = defectType.split(GlobalConst.Char.AIDescFileDefectTypeSplitChar);
                for (String defectName : defectTypeArray) {
                    DefectTypeDTO defectTypeDTO = this.defectTypeDao.queryByDefectTypeName(defectName);
                    if (Objects.isNull(defectTypeDTO)) {
                        DefectTypeEntity defectTypeEntity = new DefectTypeEntity();
                        defectTypeEntity.setDefectName(defectName);
                        defectTypeEntity.setRevision(0);
                        defectTypeEntity.setCreateTime(new Date());
                        this.defectTypeDao.insert(defectTypeEntity);

                        TaskNodeDTO taskNodeDTO = this.taskNodeDao.queryByTaskNodeName(CommonConst.TaskNode.TASK_NODE_6);
                        if (Objects.nonNull(taskNodeDTO)) {
                            NodeDefectRelationEntity entity = new NodeDefectRelationEntity();
                            entity.setDefectTypeId(defectTypeEntity.getId());
                            entity.setTaskNodeId(taskNodeDTO.getId());
                            entity.setCreateTime(new Date());
                            this.nodeDefectRelationDao.insert(entity);
                        }
                    }
                }
            }
        });
        sqlSession.commit();
        return ResponseDTO.succ();
    }
}
